import java.util.HashMap;
import java.util.Map;

/**
 * Complement Naive Bayes
 * @author alecmgo@gmail.com
 *
 */
public class CNBClassifier extends MultinomialClassifier {
  Map<Integer, Integer> numWordOccurrencesCache = new HashMap<Integer, Integer>();
 
  public CNBClassifier(MessageIterator mi) {
    super(mi);
  }
  
  /**
   * NOTE: Adding correct denominator improved accuracy 372/400 to 379/400
   */
  void calculateConditionalProbabilities() {
    double totalTermOccurrencesInCorpus = termCount.totalCount();
    for(String term : termCount.keySet()) {
      for(int newsgroup = 0; newsgroup < newsgroupCount.size(); newsgroup++) {
        double numerator = termCount.getCounter(term).totalCount() - termCount.getCount(term, newsgroup) + 1;
        double denominator = totalTermOccurrencesInCorpus - numWordOccurrencesInNewsgroup(newsgroup) + termCount.keySet().size();
        conditionalProbabilities.setCount(term, newsgroup, numerator / denominator);
      }
    }
  }
  
  double getScore(MessageFeatures message, int newsgroup) {
    double score = Math.log(newsgroupPrior.getCount(newsgroup));
    for(String term : message.body.keySet()) {
      score -= Math.log(conditionalProbabilities.getCount(term, newsgroup));
    }
    return score;
  }  
  
  /**
   * Returns the number of word occurrences in a newsgroup.  This number is cached in a Map.
   * @param newsgroup
   * @return
   */
  int numWordOccurrencesInNewsgroup(Integer newsgroup) {
    if(numWordOccurrencesCache.get(newsgroup) == null) {
      int count = 0;
      for(String term : termCount.keySet()) {
        count += termCount.getCount(term, newsgroup);
      }
      numWordOccurrencesCache.put(newsgroup, count);
    }    
    return numWordOccurrencesCache.get(newsgroup);
  }
}